#include "dllso/DllsoInit.hpp"

#include "misc/string_ext.hpp"
#include "rewine/LoaderSingleton.hpp"
#include "rewine/LoggerSingleton.hpp"
#include "rewine/impl/CDllsoImage.hpp"

#include <memory>

#include <dlfcn.h>

using std::make_pair;
using std::map;
using std::string;

namespace rewine { namespace dllso
{

/* callback */

const char *g_dllso_last_filename = nullptr;
void *g_dllso_last_header = nullptr;

void
dllso_load_callback(void *dos, const char *filename) {
    rewine::g_Logger->debug(__FLF__, "dllso callback from {}", filename);
    g_dllso_last_filename = filename;
    g_dllso_last_header = dos;
}

/* libwine */

static std::shared_ptr<void *> g_libwine;

static void
libwine_handle_deleter(void **pHandle) {
    ::dlclose(*pHandle);
    delete pHandle;
}

static bool
libwine_init()
{
    if (g_libwine.operator bool()) {
        return true;
    }

    void *handle = ::dlopen("libwine.so.1", RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
    if (!handle) {
        g_Logger->error(__FLF__, "dlopen: {}", dlerror());
        return false;
    }
    g_libwine.reset(new void *(handle), libwine_handle_deleter);

    using pfn_dll_callback_t = void (*)(void *, const char *);
    using pfn_set_dll_callback_t = void (*)(pfn_dll_callback_t);
    auto pfn_set_dll_callback = (pfn_set_dll_callback_t) dlsym(*g_libwine, "wine_dll_set_callback");
    if (!pfn_set_dll_callback) {
        g_Logger->error(__FLF__, "symbol 'wine_dll_set_callback' not found: {}", dlerror());
        return false;
    }
    pfn_set_dll_callback(dllso_load_callback);

    using pfn_rewine_init_t = void (*)(const char *, const char *);
    auto pfn_rewine_init = (pfn_rewine_init_t) dlsym(*g_libwine, "rewine_init");
    if (!pfn_rewine_init) {
        g_Logger->error(__FLF__, "symbol 'rewine_init' not found: {}", dlerror());
        return false;
    }

    pfn_rewine_init(
        "/usr/local/bin",
        "/usr/local/lib64/wine"
    );
    return true;
}

static bool
libwine_fini()
{
    g_libwine.reset();
    return true;
}

/* public */

int
wine_init() {
    libwine_init();
    return 0;
}

int
wine_fini() {
    libwine_fini();
    return 0;
}

void
fetchLastDllso(const char **pFilename, void **pDos)
{
    if (pFilename) {
        *pFilename = g_dllso_last_filename;
    }
    if (pDos) {
        *pDos = g_dllso_last_header;
    }
    g_dllso_last_filename = nullptr;
    g_dllso_last_header = nullptr;
}

} } // namespace rewine::dllso
